package ilender.dispatching;

import java.io.*;
import java.util.Set;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;

import javax.servlet.*;
import javax.servlet.http.*;

import lombok.*;

import com.google.gson.*;
import com.google.gson.stream.*;

import org.apache.ibatis.session.*;

import org.reflections.*;

import ilender.annotations.*;
import ilender.mappers.*;
import ilender.util.*;
import ilender.facebook.*;
import ilender.exceptions.*;

/**
 * Handles JSON AJAX requests.
 */
public class AjaxServlet extends HttpServlet{

	/**
	 * Accepts JSON requests in POST format.
	 *
	 * @param request The HTTP request object.
	 * @param response The HTTP response object.
	 */
	public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException{
		try(SqlSession session=MyBatisTools.openSession()){
			//Parsing the JSON data from the request.

			JsonObject requestJsonWrapper=new JsonParser().parse(request.getReader()).getAsJsonObject();
			Long fbUserID=requestJsonWrapper.getAsJsonObject().get("userId").getAsLong();
			JsonElement requestJson=requestJsonWrapper.get("request");

			ControllerTools controllerTools=new ControllerTools(
					GraphAPI.getInstance().getFBUser(fbUserID),
					session.getMapper(UsersMapper.class).find(fbUserID),
					session,
					JsonTools.createGson());

			JsonElement responseJson=null;

			//Handling batch requests.
			if(requestJson.isJsonArray()){
				responseJson=new JsonArray();
				for(JsonElement entry:requestJson.getAsJsonArray()){
					responseJson.getAsJsonArray().add(handleRequestElement(entry.getAsJsonObject(),controllerTools));
				}
				//Handling a single request.
			}else if(requestJson.isJsonObject()){
				responseJson=handleRequestElement(requestJson.getAsJsonObject(),controllerTools);
			}

			session.commit();

			//Writing the response
			try(JsonWriter writer=new JsonWriter(new OutputStreamWriter(response.getOutputStream(),"UTF8"))){
				JsonTools.createGson().toJson(responseJson,writer);
			}
		}catch(Exception e){
			JsonObject responseJson=new JsonObject();

			//Writing the errors response
			JsonArray errorsArray=new JsonArray();
			for(Throwable throwable=e;null!=throwable;throwable=throwable.getCause()){
				JsonObject singleError=new JsonObject();
				singleError.addProperty("error",throwable.toString());
				JsonArray stackTrace=new JsonArray();
				for(val traceElement:throwable.getStackTrace()){
					stackTrace.add(new JsonPrimitive(traceElement.toString()));
				}
				singleError.add("stackTrace",stackTrace);
				errorsArray.add(singleError);
			}

			responseJson.add("errors",errorsArray);
			try(JsonWriter writer=new JsonWriter(new OutputStreamWriter(response.getOutputStream(),"UTF8"))){
				JsonTools.createGson().toJson(responseJson,writer);
			}
		}
	}

	/**
	 * Handles a single request.
	 *
	 * @param request the JSON request.
	 * @param controllerTools the ControllerTools to be used in the request.
	 *
	 * @return JSON response to the request.
	 */
	public JsonElement handleRequestElement(JsonObject request,ControllerTools controllerTools){
		final String controllerName=request.get("controller").getAsString();
		final String methodName=request.get("method").getAsString();

		//Get the controller that will handle the request.
		Reflections reflections=new Reflections("ilender.controller");
		Set<Class<?>> controllers=reflections.getTypesAnnotatedWith(new Controller(){
			public String value(){return controllerName;}
			public Class<? extends Annotation> annotationType() {return Controller.class;}
		});

		if(controllers.isEmpty())
			return new JsonPrimitive(String.format("No controller named %s exists",controllerName));
		if(1<controllers.size())
			return new JsonPrimitive(String.format("Multiple controllers named %s exist",controllerName));

		Class controller=(Class)controllers.toArray()[0];

		//Get the method that will handle the request.
		try{
			Method method=controller.getMethod(methodName,JsonObject.class,ControllerTools.class);
			JsonElement requestParams=request.get("params");
			if(null==requestParams || !requestParams.isJsonObject())
				requestParams=new JsonObject();
			//Invoke the requested method.
			return (JsonElement)method.invoke(null,requestParams,controllerTools);
		}catch(NoSuchMethodException e){
			return new JsonPrimitive(String.format("No fitting method %s exists in controller %s",methodName,controllerName));
		}catch(IllegalAccessException e){
			return new JsonPrimitive(String.format("Unable to access method %s controller %s",methodName,controllerName));
		}catch(InvocationTargetException e){
			if(e.getCause() instanceof UnauthorizedGraphActionException){
				throw (UnauthorizedGraphActionException)e.getCause();
			}else{
				return new JsonPrimitive(e.getCause().getMessage());
			}
		}
		catch(AlertException e){
			return new JsonPrimitive(e.getMessage());
		}
	}
}
